using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using PitOfDespair.Dungeon;

namespace PitOfDespair
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        // Represents the character 
        PitOfDespair.GameObjects.Player player;

        bool showDebug =
#if DEBUG
 true;
#else
        false;
#endif
        // Keyboard states used to determine key presses
        KeyboardState currentKeyboardState;
        KeyboardState previousKeyboardState;

        // Gamepad states used to determine button presses
        GamePadState currentGamePadState;
        GamePadState previousGamePadState;

        IGenerationStrategy generator;
        // A movement speed for the character
        float characterMoveSpeed;
        TimeSpan previousGameTime;
       

        IList<Dungeon.Level> levels = new List<Dungeon.Level>();
        private IDictionary<PlayerIndex, int> playerLevelMap = new Dictionary<PlayerIndex, int>();
        
        Random rnd = new Random(1); //use 1 for consistency in development
        SpriteFont font;
        private Texture2D characterTexture;
        private Texture2D astroTexture;
        private Texture2D swordTexture;
        //private bool continuous;
        private enum keypressmode
        {
            continuous = 1,
            noncontinuous
        };
        /// <summary>
        /// http://snipd.net/detecting-keyboard-input-in-xna
        /// </summary>
        //Func<Keys, bool> KeyStrategy { get; set; }
        keypressmode KeyStrategy;
        public Game1()
        {
            KeyStrategy = keypressmode.noncontinuous;
            graphics = new GraphicsDeviceManager(this);

            Content.RootDirectory = "Content";
            generator = GetGenerationStrategy();
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            // Initialize the character class

            // Set a constant character move speed
            characterMoveSpeed = 0.2f;

            //Enable the FreeDrag gesture.
            //TouchPanel.EnabledGestures = GestureType.FreeDrag;

            base.Initialize();

        }

        private Texture2D glyphs;
        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            graphics.PreferredBackBufferHeight = 1080;
            graphics.PreferredBackBufferWidth = 1920;
            graphics.ApplyChanges();

            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            wallTexture = new Texture2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
            wallTexture.SetData<Color>(new Color[] {Color.White});
            // Load the character resources 
            Vector2 characterPosition = new Vector2(GraphicsDevice.Viewport.TitleSafeArea.X, GraphicsDevice.Viewport.TitleSafeArea.Y + GraphicsDevice.Viewport.TitleSafeArea.Height / 2);
            characterTexture = Content.Load<Texture2D>("Images\\Character");
            astroTexture = Content.Load<Texture2D>("Images\\Astrological");
            swordTexture = Content.Load<Texture2D>("Images\\icon-26501_640");
            // TODO: use this.Content to load your game content here
            font = Content.Load<SpriteFont>("SpriteFonts\\Fire");
            glyphs = Content.Load<Texture2D>("Images\\Six_glyphs");
            player = new PitOfDespair.GameObjects.Player(0, 0, 100, 90, characterTexture, characterTexture.Bounds, PlayerIndex.One);
            playerLevelMap.Add(PlayerIndex.One, 0);
            levels.Add(new Dungeon.Level(rnd, player, generator));
            levels[0].AddObjectRandom(new Character(0, 0, 0, 2, 2, characterTexture, characterTexture.Bounds));
            swordsManTexture = Content.Load<Texture2D>(@"Images\war-36681_640");

        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }
        private void AgeParticles(GameTime gameTime, List<ParticleData> particleList)
        {
            IList<ParticleData> toModify = new List<ParticleData>();

            for (int i = 0; i < particleList.Count; i++)
            {
                var p = particleList[i];
                p.MaxAge -= gameTime.ElapsedGameTime.Milliseconds;
                toModify.Add(p);
            }
            particleList.Clear();
            foreach (var p in toModify)
            {

                particleList.Remove(p);
                if (p.MaxAge > 0)
                    particleList.Add(p);

            }

        }
        void AddExplosion(Vector2 explosionPos, int numberOfParticles, float size, float maxAge, GameTime gameTime)
        {
            for (int i = 0; i < numberOfParticles; i++)
            {
                AddExplosionParticle(explosionPos, size, maxAge, gameTime);
            }
        }

        void AddExplosionParticle(Vector2 explosionPos, float explosionSize, float maxAge, GameTime gameTime)
        {
            ParticleData particle = new ParticleData();

            particle.OrginalPosition = explosionPos;
            particle.Position = particle.OrginalPosition;

            particle.BirthTime = (float)gameTime.TotalGameTime.TotalMilliseconds;
            particle.MaxAge = maxAge;
            particle.Scaling = 0.25f;
            particle.ModColor = Color.White;

            float particleDistance = (float)rnd.NextDouble() * explosionSize;
            Vector2 displacement = new Vector2(particleDistance, 0);
            float angle = MathHelper.ToRadians(rnd.Next(360));
            displacement = Vector2.Transform(displacement, Matrix.CreateRotationZ(angle));

            particle.Direction = displacement;
            particle.Accelaration = 3.0f * particle.Direction;

            particleList.Add(particle);
        }
        public struct ParticleData
        {
            public float BirthTime;
            public float MaxAge;
            public Vector2 OrginalPosition;
            public Vector2 Accelaration;
            public Vector2 Direction;
            public Vector2 Position;
            public float Scaling;
            public Color ModColor;
        }

        private void DrawExplosion()
        {
            if (particleList.Count > 0)
                Console.WriteLine("drawing " + particleList.Count + " particles");
            for (int i = 0; i < particleList.Count; i++)
            {

                ParticleData particle = particleList[i];
                spriteBatch.Draw(swordTexture, particle.Position, null, particle.ModColor, i, new Vector2(256, 256), particle.Scaling, SpriteEffects.None, 1);
            }
        }
        List<ParticleData> particleList = new List<ParticleData>();

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            
            // Save the previous state of the keyboard and game pad so we can determinesingle key/button presses
            previousGamePadState = currentGamePadState;
            previousKeyboardState = currentKeyboardState;
            // Read the current state of the keyboard and gamepad and store it
            currentKeyboardState = Keyboard.GetState();
            currentGamePadState = GamePad.GetState(PlayerIndex.One);
            if (particleList.Any())
            {
                AgeParticles(gameTime, particleList);
            }
            //Update the player
            UpdatePlayer(gameTime, KeyStrategy);
            // Allows the game to exit
            if (currentGamePadState.Buttons.Back == ButtonState.Pressed || currentKeyboardState.GetPressedKeys().Contains(Keys.Escape))
                this.Exit();
            //if (currentGamePadState.Buttons.Start == ButtonState.Pressed || currentKeyboardState.GetPressedKeys().Contains(Keys.Enter))
            //    this.player = new GameObjects.GameObject(
            //        new Texture2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color), new Rectangle(1, 1, 1, 1));
            if (currentGamePadState.Buttons.BigButton == ButtonState.Pressed || ((currentKeyboardState.IsKeyDown(Keys.RightAlt) && previousKeyboardState.IsKeyDown(Keys.RightAlt)) && (currentKeyboardState.IsKeyDown(Keys.C) && !previousKeyboardState.IsKeyDown(Keys.C))))
            {
                //continuous = !continuous;
                if (KeyStrategy == keypressmode.continuous)
                {
                    KeyStrategy = keypressmode.noncontinuous;
                }
                else
                {
                    KeyStrategy = keypressmode.continuous;
                }

                //if (continuous)
                    //KeyStrategy = IsKeyPressedContinuous;
                //else
                    //KeyStrategy = IsKeyPressed;
            }
            if (currentGamePadState.Buttons.A == ButtonState.Pressed || currentKeyboardState.GetPressedKeys().Contains(Keys.Space))
            {

                levels.Clear();

                levels.Add(new Dungeon.Level(rnd, player, GetGenerationStrategy()));
                levels[0].AddPlayer(player);
                playerLevelMap[PlayerIndex.One] = 0;
            }


            base.Update(gameTime);
        }
        IGenerationStrategy GetGenerationStrategy()
        {
            return new PerfectMaze(rnd, 16, 16);
        }
        void CheckDescent(PlayerIndex i, Func<Dungeon.Level, Func<PlayerIndex, WallType>> movement)
        {
            var lvl = playerLevelMap[i];
            var l = levels[lvl];
            
            var wallMove = movement(levels[lvl])(i);
            if (wallMove == WallType.Descend)
            {
                var previous = playerLevelMap[i];
                if (levels.Count <= previous + 1)
                {
                    var level = new Level(rnd, player, GetGenerationStrategy(), levels[previous]);
                    levels.Add(level);
                    level.AddObjectRandom(new Character((byte)(levels.Count - 1), 0, 0, 2, 2, characterTexture, characterTexture.Bounds));
                }
                else
                {

                    levels[previous + 1].AddPlayer(player, levels[previous]);
                }
                playerLevelMap[i] = previous + 1;

            }
            else if (wallMove == WallType.Ascend)
            {

                var previous = playerLevelMap[i];
                if (previous < 1) //can't go up from first floor
                    return;

                playerLevelMap[i] = previous - 1;
                levels[previous - 1].AddPlayer(player, levels[previous]);
            }
        }

    

        private void UpdatePlayer(GameTime gameTime, keypressmode KeyStrategy)
        {
            // Windows Phone Controls
            //while (TouchPanel.IsGestureAvailable)
            //{
            //    GestureSample gesture = TouchPanel.ReadGesture();
            //    if (gesture.GestureType == GestureType.FreeDrag)
            //    {
            //        player.Position += gesture.Delta;
            //    }
            //}

            // Get Thumbstick Controls

            //character.Position.X += currentGamePadState.ThumbSticks.Left.X * characterMoveSpeed;
            //character.Position.Y -= currentGamePadState.ThumbSticks.Left.Y * characterMoveSpeed;
            
        
            



            //TODO: allow/disallow diagonal moves?

            
            // Use the Keyboard / Dpad

            Func<Keys, bool> isKeyPressed;
            if (KeyStrategy == keypressmode.noncontinuous)
            {
                isKeyPressed = k => currentKeyboardState.IsKeyDown(k) &&
                (previousKeyboardState == null || previousKeyboardState.IsKeyDown(k) == false);
            }
            else
            {
                isKeyPressed = k => currentKeyboardState.IsKeyDown(k);
            }

            if (isKeyPressed(Keys.Left) || isKeyPressed(Keys.A) ||
            currentGamePadState.DPad.Left == ButtonState.Pressed)
            {
                if (KeyStrategy == keypressmode.noncontinuous || (KeyStrategy == keypressmode.continuous && DateTime.Now.TimeOfDay.Subtract(previousGameTime).Milliseconds > 350))
                {
                    previousGameTime = DateTime.Now.TimeOfDay;
                    CheckDescent(PlayerIndex.One, l => l.MoveWest);
                }

            }

            else
            if (isKeyPressed(Keys.Right) || isKeyPressed(Keys.D) ||
            currentGamePadState.DPad.Right == ButtonState.Pressed)
            {
                if (KeyStrategy == keypressmode.noncontinuous || (KeyStrategy == keypressmode.continuous && DateTime.Now.TimeOfDay.Subtract(previousGameTime).Milliseconds > 350))
                {
                    previousGameTime = DateTime.Now.TimeOfDay;
                    CheckDescent(PlayerIndex.One, l => l.MoveEast);
                }
            }

            else
            if (isKeyPressed(Keys.Up) || isKeyPressed(Keys.W) ||
            currentGamePadState.DPad.Up == ButtonState.Pressed)
            {
                if (KeyStrategy == keypressmode.noncontinuous || (KeyStrategy == keypressmode.continuous && DateTime.Now.TimeOfDay.Subtract(previousGameTime).Milliseconds > 350))
                {
                    previousGameTime = DateTime.Now.TimeOfDay;
                    CheckDescent(PlayerIndex.One, l => l.MoveNorth);
                }
            }

            else
            if (isKeyPressed(Keys.Down) || isKeyPressed(Keys.S) ||
            currentGamePadState.DPad.Down == ButtonState.Pressed)
            {
                if (KeyStrategy == keypressmode.noncontinuous || (KeyStrategy == keypressmode.continuous && DateTime.Now.TimeOfDay.Subtract(previousGameTime).Milliseconds > 350))
                {
                    previousGameTime = DateTime.Now.TimeOfDay;
                    CheckDescent(PlayerIndex.One, l => l.MoveSouth);
                }
            }



            // Make sure that the character does not go out of bounds
            //character.Position.X = MathHelper.Clamp(character.Position.X, 0, GraphicsDevice.Viewport.Width - character.Width);
            //character.Position.Y = MathHelper.Clamp(character.Position.Y, 0, GraphicsDevice.Viewport.Height - character.Height);
        }

        static Texture2D wallTexture;
        private Texture2D swordsManTexture;
        IDisposable DrawSimple(Rectangle destination, Color color)
        {
            
            //Color[] existing=new Color[1];
            //wallTexture.GetData<Color>(existing);
            //var simple = new Texture2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
            
            //if (existing.FirstOrDefault() != color)
              //  wallTexture.SetData<Color>(new[] { color });
            spriteBatch.Draw(wallTexture, destination, color); //white for no tinting

            return wallTexture;
        }
        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);


            this.spriteBatch.Begin(SpriteSortMode.Immediate,BlendState.AlphaBlend);
            if(player!=null)spriteBatch.DrawString(font, "Score:" + player.Score,new Vector2(20,20),Color.Black);
            spriteBatch.DrawString(font, "Level " + (playerLevelMap[PlayerIndex.One] + 1).ToString()+" - "+KeyStrategy.ToString(), new Vector2(GraphicsDevice.Viewport.Bounds.Center.X, 10), Color.Black);
            if(showDebug) spriteBatch.DrawString(font, "IsRunningSlowly=" +gameTime.IsRunningSlowly.ToString(), new Vector2(GraphicsDevice.Viewport.Bounds.Center.X, 25), Color.Black);
            if (player != null)
            {
                DrawSimple(new Rectangle(0, 0, GraphicsDevice.Viewport.Width/2 - 40, 20), Color.Black);
                var maxLifeRightPos = GraphicsDevice.Viewport.Width/2 - 42;
                var currentLifeRightPos =Math.Min( maxLifeRightPos*player.Health / player.MaxHealth,maxLifeRightPos);

                DrawSimple(new Rectangle(1, 1, currentLifeRightPos, 19), Color.Red);
            }
            var lvl = playerLevelMap[PlayerIndex.One];
            levels[lvl].Draw(40, (r, c) => DrawSimple(new Rectangle(r.X + 100, r.Y + 100, r.Width, r.Height), c), (t, r, c) => spriteBatch.Draw(t, new Rectangle(r.X + 100, r.Y + 100, r.Width, r.Height), c));
            // Draw the character
            if(showDebug && gameTime.TotalGameTime<TimeSpan.FromSeconds(8))
            {
                spriteBatch.Draw(glyphs, new Vector2(10, 70), PitOfDespair.GameObjects.ImageMaps.GetTSquiggle(), Color.White);
                spriteBatch.Draw(glyphs, new Vector2(10, 120), PitOfDespair.GameObjects.ImageMaps.GetKK(), Color.White);
                spriteBatch.Draw(glyphs, new Vector2(10, 160), PitOfDespair.GameObjects.ImageMaps.GetLambda(),
                                 Color.White);
                //spriteBatch.Draw(glyphs,new Vector2(10,180),PitOfDespair.GameObjects.ImageMaps.)
                spriteBatch.Draw(astroTexture, new Vector2(GraphicsDevice.Viewport.Bounds.Right - 100, 10),
                                 PitOfDespair.GameObjects.ImageMaps.GetSol(), Color.White);
                spriteBatch.Draw(astroTexture, new Vector2(GraphicsDevice.Viewport.Bounds.Right - 100, 80),
                                 PitOfDespair.GameObjects.ImageMaps.GetMercurius(), Color.White);
                spriteBatch.Draw(swordTexture, new Rectangle(10, 240,30,30),
                                 Color.White);
                spriteBatch.Draw(swordsManTexture, new Rectangle(10, 280, 35, 35),Color.White);
            }

            this.spriteBatch.End();
            // foreach (var item in resources)
            //   item.Dispose();

            base.Draw(gameTime);
        }
    }
}
